今天的主題其實在之前開始介紹如何使用 Git 時就可以先說明,不過還是希望能先把基本觀念一次說明到位,讓讀者先對 Git 有一定的認識再說,畢竟設定類的說明,隨時補都來得及。
回到主題,實務上的專案中,總是會有一些「設定檔」、「編譯檔」、「日誌(log)」,之類不影響專案本體的內容,他們並不需要 (有些甚至不可以) 被記錄到儲存庫中,該如何定義那些「不想被版控」的資料,會是個重要的問題。
為此 Git 提供了一個方式讓我們去定義這些內容:設定 .gitignore
檔案。
首先來說文解字一下, ignore 中文是「忽略」的意思,所以 .gitignore
是用來定義讓 Git 忽略的資料的檔案。
想定義能讓 Git 忽略的資料也很簡單:
.gitignore
。 (切記,是 檔案 ,不是 資料夾 )舉例來說,目前工作目錄中的檔案都是剛新增的檔案,是 Git 「尚未追蹤」 的狀態:
如果我想忽略 data.log
以及 app.log
,就直接按照上述步驟操作(多個檔案「換行」寫入即可):
在 VSCode 中,工作目錄如果看到檔案呈現灰色,則代表 Git 正在忽略這個檔案。
只要把 .gitignore
字樣也寫進 .gitignore
裡面,這個檔案也會被忽略:
不知道讀者有沒有從截圖中注意到,此時的 .gitignore
並沒有被加入版控 (也就是尚未被 commit)?
其實只要工作目錄裡面有 .gitignore
,Git 就會自動去讀取它的內容,並且自動忽略符合內容的檔案。 即使 .gitignore
檔案沒有被版控也會有效果!
不過要設定成功必須達成兩個條件:
.gitignore
的內容必須符合撰寫規則 (後續會詳細說明)。第二點尤其重要,如果檔案已經被加入索引 (就是暫存區),甚至已經被 commit 到儲存庫中 (註1),那麼無論怎麼在 .gitignore
裡面寫資料,Git 都沒辦法排除這個檔案。
就像你已經認識了一個人,結果有人告訴你:「別認識他,忘掉他吧!」
常理來說是很難做到的吧...
註1. 觀念重申:已經被 commit 的檔案,在索引(暫存區)中也會有資料。
有個重要的事情先講:
.gitignore
中可以寫註解,不過如果要寫註解,「不可以」寫在跟檔名同一行,否則 Git 會把你想寫的註解視為檔名,導致忽略失效。
為了讓大家可以比較好瀏覽,「下方註解故意寫在『無效的位置』」 ,請各位千萬注意!!!
.env # 環境設定檔
.config # 配置檔案
.DS_Store # macOS Finder 產生的檔案
app.log # 名叫 app 的 log 檔案
*.log # 忽略所有以 .log 結尾的檔案
*.tmp # 忽略所有以 .tmp 結尾的檔案
*.log # 忽略所有以 .log 結尾的檔案
!important.log # 但不忽略 important.log
hello.* # 忽略任何叫 hello 的檔案,包含 hello.txt、hello.java ...
image* # 忽略所有以 image 開頭命名的資料(包含檔案與目錄)
/Test # 只忽略根目錄 Test ,不忽略子目錄的 Test,例如 Vue/Test 就不會被忽略
.vscode/ # Visual Studio Code 設定檔
.idea/ # IntelliJ IDEA 設定檔
node_modules/ # Node.js 的依賴模組
build/ # 編譯或建置的結果目錄
dist/ # 發布版程式碼的目錄
# 這是一個有效註解
.vscode/ # 寫在這裡是無效註解,所以上述範例註解會直接使設定無效!!
以上大概是 .gitignore
常見的寫法,如果還有特殊需求,請各位讀者參考 Git 官網 對於設定的說明。
如果覺得在不同的工作目錄設定固定的「忽略檔案」很麻煩,可以直接設定「全域」的 .gitignore
。
執行步驟如下:
git config --global core.excludesfile "~/.gitignore_global"
或是直接在 .gitconfig
檔 [core]
底下加這行,跟執行指令意思一樣 (註1)。
[core]
excludesfile = ~/.gitignore_global
根據不同系統,新增 .gitignore_global
檔案 (註2):
Mac 請到這個路徑設定: ~/.gitignore_global
Windows 在這個路徑: C:\Users\你的使用者名字\.gitignore_global
在 .gitignore_global
設定的內容,即為「全域 .gitignore
」。
註1. 如果不知道
.gitconfig
檔案如何操作 ,可以參考 Git 初始化設定文章 常見問題 「第四題」。註2. 上述的「檔名」與「路徑」都可以自己定義,只要第 1、2 步有辦法對應即可,上面路徑設定只是為了把 全域
.gitignore
跟.gitconfig
放在一樣的路徑。
開頭有提到,已經被版控的檔案會在 索引 (暫存區) 存有資料,所以會導致 .gitignore
無法忽略。
要忽略已被版控的檔案,有三件事情要做 (註1):
.gitignore
內設定要忽略的檔名。git commit
提交一個版本。可能會有讀者疑惑第三步的動作,明明沒有編輯檔案,是要 commit 什麼東西啊,而且難道不用先 git add
嗎?!
來說明一個小觀念,讓大家細細品嘗(?):
在 Git 的世界中,只要有 操作工作目錄或是索引 的「行為」,都會導致「資料狀態」被改變。
資料原本 commit 好的 穩定 狀態如果被改變了,Git 就會認為應該要把這個「被改變的狀態」給記錄起來。
資料:兄弟阿~ 挖應該嘎你喜無冤無仇啦齁,林北 commit 嘎 喝ㄙㄟˋ喝ㄙㄟˇ ,你無逮無治嘎挖亂叮噹...
我們在執行 git add
時,會把「新增或編輯的檔案狀態」加入索引。
這裡第二步的行為,則是把「移除索引的檔案狀態」加到索引。 (<=這句話應該最難懂)
兩者在 Git 看來,都是有東西 被加到暫存區 的行為,所以下一步,就可以直接執行 commit 了!
詳細指令操作如下:
修改 .gitignore
,並視需求決定是否跟著「第三步」一起 commit。
執行下列指令 (註2),把檔案移出索引。
git rm --cached 檔名
git commit -m "將 XXX 移出「暫存區」"
如果都順利完成,此時的檔案應該已經能被 Git 忽略了!
註 1. 上述內容也可以在 官網 找到說明。
註 2.
rm (remove)
用來告訴 Git 要刪掉檔案,--cached
則是告訴 Git 不是要從「工作目錄」中刪除,只要刪除「索引」的資料就好。註 3. 再說明一次:透過
git
的指令將資料「移出索引」的行為,資料被移出 的狀態會被加到「暫存區」,所以可以直接 Commit。
我們已經知道 git rm --cached 檔名
是把檔案「移出索引」的指令。
不過執行完指令之後的檔案,有沒有寫進 .gitignore
,git status
出現的資料狀態會不一樣哦!
舉例來說,假設我忘記把 data.log
放到 .gitignore
就 commit 了,現在想把它移除:
於是我執行了這個指令:
git rm --cached data.log
「沒有」把 data.log
寫進 .gitignore
時,執行 git status
會出現這樣的資料:
data.log
在 「可被提交」 區域呈現 「已刪除」 狀態 (綠字那筆),
同時也會出現在 「未追蹤檔案」 區域 (紅字那筆)。
把 data.log
寫進 .gitignore
,再執行一次 git status
:
神奇的事情出現了,data.log
竟然從 「未追蹤檔案」 區域消失了!!
給大家 10 秒鐘思考是什麼原因。好時間到!
記得「未追蹤」檔案的定義嗎?
只要不在索引(暫存區)內的檔案,就是「未追蹤」檔案。
由於 git rm --cached
就是「把檔案從索引移除」的指令,執行完指令後,檔案當然就會是未追蹤狀態。
要是未追蹤的檔案再度被寫到 .gitignore
中,就會讓 Git 會自動「忽略」這個檔案,就好像 Git 「看不到」這個檔案一般,既然對 Git 來說檔案根本就不存在,那也不用探討追蹤與否的問題了。
不過此時的 Git 確實知道有一個叫 data.log
被「移出索引」,而且移出索引的「狀態」還沒有被「紀錄」,所以 git status
會顯示有個已經被刪除的檔案,而且它還沒被 commit 。
我們因此能得知,雖然 .gitignore
不是索引,不過它也會影響到 Git 讀取專案檔案的狀態。
.git/index
索引 (a.k.a. 暫存區) 用來紀錄要讓 Git 「追蹤」的資料;
.gitignore
用來紀錄要讓 Git 「忽略」的資料。
git rm --cached
移出檔案,會影響之前 commit 的檔案嗎?不會。
用另一個角度來看這個指令,執行完指令之後的檔案,就像剛跟 Git 談完分手一樣,兩者變成了最熟悉的陌生人。
他們之間經歷的回憶,就像已經 commit 的內容一樣,Git 不會輕易忘記。
這樣的模式也很合理,commit 行為在 Git 的世界應該算是最神聖的事情,這會把關鍵的時間點封裝成重要的記憶。
如果有任何一個指令,可以一口氣把歷史中 commit 過的某個檔案一次清除,可能也就失去了「版控」的意義。
所以 git rm --cached
依然是一個讓時間「往前走」的指令,他只是某個檔案跟 Git 分手的時刻,但過去被記錄的點點滴滴,不會輕易的被 Git 所遺棄 。
這樣說起來,執行完指令後那個 commit 的行為,就是要發文紀錄一下自己分手了嗎?!
奇怪...怎麼寫完這段話...眼角微微泛淚...
是洋蔥,我加了洋蔥
又是那個老答案:「看需求」。
如果你使用 Git 都是一個人在版控,而且每個工作目錄需要忽略的檔案大同小異,那其實只要建立好 「全域 .gitignore
」 就可以,不一定要在每個專案都定義 .gitignore
,就也不會有要不要把 .gitignore
加入版控的問題了。
不過如果是跟團隊合作某個專案,為了讓整個團隊能擁有一致的「忽略清單」,這種情況就會建議把 .gitignore
加到版控中。
這樣的好處是,如果團隊的專案因為某個需求需要增加「要被忽略的檔案」,只要有人在專案的 .gitignore
檔案定義好,所有人同步之後就搞定了,省得每個人還要額外設定。